﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using System.IO;


namespace Megaman
{
    class Level : IDisposable
    {
        // Level content.        
        public ContentManager Content
        {
            get { return content; }
        }
        ContentManager content;
        public Megaman Player;
        private Background background;
        private List<SceneTransition> transitions = new List<SceneTransition>();

        private Tile[,] tiles;
        private String TextFilePath;
        private String BasePath;


        private BackgroundMusic music;

        // Key locations in the level.        
        private Vector2 start;
        private Point exit = InvalidPosition;
        private static readonly Point InvalidPosition = new Point(-1, -1);
        private List<Enemy> enemies = new List<Enemy>();
        private List<Bullet> bullets = new List<Bullet>();

        public SpriteFont font;


        public bool ReachedExit
        {
            get { return reachedExit; }
        }
        bool reachedExit;

        Stage stage;
        // Level game state.
        private Random random = new Random(354668); // Arbitrary, but constant seed
        public Vector2 cameraPosition;

        private bool playingMusic = false;

        public Level(IServiceProvider serviceProvider, string BasePath, string path, Stage stage)
        {
            content = new ContentManager(serviceProvider, "Content");
            this.TextFilePath= path;
            this.BasePath = BasePath;
            this.stage = stage;

            LoadTiles();

            background = new Background( BasePath, content);


            String introLengthPath = String.Format(BasePath + "/Music/introLength.txt");
            introLengthPath = Path.Combine(StorageContainer.TitleLocation, "Content/" + introLengthPath);
            float introLength;
            StreamReader reader = new StreamReader(introLengthPath);
            String line = reader.ReadLine();
            introLength = float.Parse(line);

            music = new BackgroundMusic(introLength,Content.Load<Song>(BasePath+"\\Music\\Intro"),Content.Load<Song>(BasePath + "\\Music\\Loop"));
               
            font = content.Load<SpriteFont>("Fonts/font");
            

        }


        #region IDisposable Members

        public void Dispose()
        {
            content.Unload();
        }
                #endregion

        private void LoadTiles()
        {
            int width;
            List<string> lines = new List<string>();
            using (StreamReader reader = new StreamReader(TextFilePath))
            {
                string line = reader.ReadLine();
                width = line.Length;
                while (line != null)
                {
                    lines.Add(line);
                    if (line.Length != width)
                        throw new Exception(String.Format("Las linea{0} tienen distintos tamaños.",lines.Count));
                    line = reader.ReadLine();
                }
            }
            
            tiles = new Tile[width, lines.Count];

            for(int i = 0; i < Width; i++)
            {   
                for( int j = 0; j < Height; j++ )
                {
                    char tileType = lines[j][i];
                    tiles[i,j] = LoadTile(tileType, i,j);

                }
            }

        }

        private Tile LoadTile(char tileType, int i, int j)
        {
            switch (tileType)
            {
                case '.':
                    return new Tile(TileCollision.Passable);
                case 'X':
                    return LoadExitTile(i, j);
                case '-':
                    return LoadTile("Platform", TileCollision.Platform);
                // Various enemies
                case 'A':
                    return LoadEnemyTile(i, j, "A");
                case 'B':
                    return LoadEnemyTile(i, j, "B");
                case 'C':
                    return LoadEnemyTile(i, j, "C");
                case 'D':
                    return LoadEnemyTile(i, j, "D");
                // Platform block
                case '~':
                    return LoadVarietyTile("BlockB", 2, TileCollision.Platform);
                // Passable block
                case ':':
                    return LoadVarietyTile("BlockB", 2, TileCollision.Passable);

                // Player 1 start point
                case '1':
                    return LoadStartTile(i, j);

                // Impassable block
                case '#':
                    return LoadVarietyTile("BlockA", 7, TileCollision.Impassable);

                // Unknown tile type character
                default:
                    throw new NotSupportedException(String.Format("Unsupported tile type character '{0}' at position {1}, {2}.", tileType, i, j));
            }
        }



        private Tile LoadTile(String name, TileCollision collision)
        {
            Tile resp = new Tile(content.Load<Texture2D>(BasePath+"\\Tiles\\"+name),collision);
            return resp;
        }

        private Tile LoadVarietyTile(string baseName, int variationCount, TileCollision collision)
        {
            int index = random.Next(variationCount);
            return LoadTile(baseName + index, collision);
        }

        private Tile LoadStartTile(int i, int j)
        {
            if (Player != null)
                throw new NotSupportedException("A level may only have one starting point.");

            start = RectangleExtensions.GetBottomCenter(GetBounds(i, j));
            Player = new Megaman(this, start);

            return new Tile(TileCollision.Passable);

        }

        private Tile LoadExitTile(int i, int j)
        {
            if(exit != InvalidPosition )
                throw new NotSupportedException("A level may only have one exit.");

            exit = GetBounds(i, j).Center;

            return LoadTile("Exit", TileCollision.Passable);
        }

        private const int DEFAULT_DAMAGE = -2;
        /// <summary>
        /// Instantiates an enemy and puts him in the level.
        /// </summary>
        private Tile LoadEnemyTile(int x, int y, string spriteSet)
        {
            Vector2 position = RectangleExtensions.GetBottomCenter(GetBounds(x, y));
            enemies.Add(new Enemy(this, position, spriteSet, DEFAULT_DAMAGE));

            return new Tile(null, TileCollision.Passable);
        }

        public TileCollision GetCollision(int x, int y)
        {
            // Prevent escaping past the level ends.
            if (x < 0 || x >= Width)
                return TileCollision.Impassable;
            // Allow jumping past the level top and falling through the bottom.
            if (y < 0 || y >= Height)
                return TileCollision.Passable;

            return tiles[x, y].Collision;
        }

        public Rectangle GetBounds(int x, int y)
        {
            return new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height);
        }

        /// <summary>
        /// Width of level measured in tiles.
        /// </summary>
        public int Width
        {
            get { return tiles.GetLength(0); }
        }

        /// <summary>
        /// Height of the level measured in tiles.
        /// </summary>
        public int Height
        {
            get { return tiles.GetLength(1); }
        }

        public void Update(GameTime gameTime)
        {
            if (!Player.IsAlive)
            {

                if (stage.Lives > 1)
                {
                    stage.Lives--;
                    stage.ReloadLevel();
                }
                else
                    stage.ExitedLevel();
            }
            else
            {
                if (Player.exploding())
                    MediaPlayer.Stop();

                else
                    music.Update(gameTime);
                
                    HandleInput();
                    Player.Update(gameTime);
                    UpdateEnemies(gameTime);
                
                

            }
        }

        private bool playingLoop = false;

        public void HandleInput()
        {
            KeyboardState state = Keyboard.GetState();
            GamePadState gpad = GamePad.GetState(PlayerIndex.One);
            if (state.IsKeyDown(Keys.Escape) || gpad.IsButtonDown(Buttons.Back))
                stage.ExitedLevel();
        }
        private void UpdateEnemies(GameTime gameTime)
        {
            foreach (Enemy enemy in enemies)
            {
                if (!enemy.IsDead())
                {
                    enemy.Update(gameTime);


                    if (enemy.BoundingRectangle.Intersects(Player.BoundingRectangle))
                    {
                        OnPlayerHit(enemy);
                    }

                    foreach (Bullet bullet in bullets)
                    {
                        if (enemy.BoundingRectangle.Intersects(bullet.BoundingRectangle))
                        {
                            int enemyDamage = enemy.Damage;
                            int bulletenergy = bullet.energy;
                            enemy.Damage += bulletenergy;
                            if (enemy.Damage >= 0)
                            {
                                enemy.KillEnemy();
                                enemy.dyingSound.Play();
                            }
                            bullet.energy += enemyDamage;
                            if(bullet.energy <= 0)
                                bullet.desintegrated = true;

                        }
                    }
                }
            }
            List<int> indices = new List<int>();
            int i = -1;
            foreach (Bullet bullet in bullets)
            {
                i++;
                if (!bullet.desintegrated)
                {
                    bullet.Update(gameTime);

                }
                else
                    indices.Add(i);

            }


            foreach (int j in indices)
            {
                if(j < bullets.Count )
                    bullets.RemoveAt(j);
            }
        }


        public void BulletFired(Bullet bullet)
        {
            bullets.Add(bullet);
        }

        

        private void OnPlayerHit(Enemy enemy)
        {
            Player.Hit(enemy);
        }
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
            background.Draw(spriteBatch, cameraPosition);
            spriteBatch.End();

            ScrollCamera(spriteBatch.GraphicsDevice.Viewport);
            Matrix cameraTransform = Matrix.CreateTranslation(-cameraPosition.X, -cameraPosition.Y, 0.0f);

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None, cameraTransform);
            DrawTransitions(spriteBatch);
            DrawTiles(spriteBatch);
            DrawEnemies(spriteBatch, gameTime);

       
            Player.Draw(gameTime, spriteBatch);
            foreach (Bullet bullet in bullets)
            {
                bullet.Draw(spriteBatch, gameTime);
            }
            spriteBatch.End();

        }


        private void DrawTransitions(SpriteBatch spriteBatch)
        {
            foreach (SceneTransition t in transitions)
            {
                if(t.isIncluded(cameraPosition,new Vector2(
                    spriteBatch.GraphicsDevice.Viewport.Width,spriteBatch.GraphicsDevice.Viewport.Height)))
                {
                    spriteBatch.Draw(t.Texture, t.getDrawPosition(), Color.White);
                }
            }
        }
        private void ScrollCamera(Viewport viewport)
        {
            const float viewMargin = 0.35f;
            const float yMargin = 0.45f;

            float marginHeight = viewport.Height * yMargin;
            float marginTop = cameraPosition.Y + marginHeight;
            float marginBottom = cameraPosition.Y + viewport.Height - marginHeight;

            float marginWidth = viewport.Width * viewMargin;
            float marginLeft = cameraPosition.X + marginWidth;
            float marginRight = cameraPosition.X + viewport.Width - marginWidth;

            float cameraMovement = 0.0f;
            if (Player.Position.X < marginLeft)
                cameraMovement = Player.Position.X - marginLeft;
            else if (Player.Position.X > marginRight)
                cameraMovement = Player.Position.X - marginRight;

            float cameraYMovement = 0f;
            if (Player.Position.Y < marginTop)
                cameraYMovement = Player.Position.Y - marginTop;
            else if (Player.Position.Y >= marginBottom)
                cameraYMovement = Player.Position.Y - marginBottom;

            
            float maxCameraPosition = Tile.Width * Width - viewport.Width;
            float maxYCameraPosition = Tile.Height * Height - viewport.Height;

            if (Player.Position.Y > (maxYCameraPosition + viewport.Height))
            {
                if(!Player.exploding())
                    Player.Killed();
            }

            cameraPosition.X = MathHelper.Clamp(cameraPosition.X + cameraMovement, 0.0f, maxCameraPosition);
            cameraPosition.Y = MathHelper.Clamp(cameraPosition.Y + cameraYMovement, 0.0f, maxYCameraPosition);


        }
        /// <summary>
        /// Draws each tile in the level.
        /// </summary>
        private void DrawTiles(SpriteBatch spriteBatch)
        {
            // Calculate the visible range of tiles.
            int left = (int)Math.Floor(cameraPosition.X / Tile.Width);
            int right = left + spriteBatch.GraphicsDevice.Viewport.Width / Tile.Width;
            int top = (int)Math.Floor(cameraPosition.Y / Tile.Height);
            int bottom = top + spriteBatch.GraphicsDevice.Viewport.Height / Tile.Height;

            right = Math.Min(right, Width - 1);
            bottom = Math.Min(bottom, Height - 1);

            // For each tile position
            for (int y = top; y <= bottom; ++y)
            {
                for (int x = left; x <= right; ++x)
                {
                    // If there is a visible tile in that position
                    Texture2D texture = tiles[x, y].Texture;
                    if (texture != null)
                    {
                        // Draw it in screen space.
                        Vector2 position = new Vector2(x, y) * Tile.Size;
                        spriteBatch.Draw(texture, position, Color.White);
                    }
                }
            }
        }

        private void DrawEnemies(SpriteBatch spriteBatch, GameTime gameTime)
        {
            int left = (int)cameraPosition.X;
            int right = (int)cameraPosition.X + spriteBatch.GraphicsDevice.Viewport.Width;
            int top = (int) cameraPosition.Y;
            int bottom = (int)cameraPosition.Y + spriteBatch.GraphicsDevice.Viewport.Height;
            foreach (Enemy enemy in enemies)
            {
                if (enemy.Position.X >= (left - 100 ) && enemy.Position.X <= (right + 100) &&
                    enemy.Position.Y >= (top - 100) && enemy.Position.Y <= (bottom + 100 ))
                {
                    enemy.Draw(gameTime, spriteBatch);
                }
            }
        }

    }
}
